require "net/http"
require "uri"
require "cgi"

module TclBot
  module Commands
    module HttpCommand
      # Returns a Tcl list indexed as follows:
      #   0. Response code
      #   1. Response headers in {key value key value} format
      #   2. Response body
      #
      # Raises an exception if the response body is > 500 KB
      # or if a request has been made in the past 5 seconds.
      def tcl_http(verb, url, *params)
        enforce_http_request_limit!
        
        uri  = URI.parse(url)
        http = Net::HTTP.new(uri.host, uri.port)
        req  = http_request_for(verb, uri, params)        
        res  = http.start do
          http.request(req) do |res|
            raise "response body too large" if res.body.length > 500 * 1024
          end
        end
        
        @last_http_request = Time.now
        interp._(res.code, interp._(*res.to_hash.to_a.flatten), res.body)
      end
      
      protected
        def enforce_http_request_limit!
          if @last_http_request && (time_since_last_request = Time.now - @last_http_request) <= 5
            raise "can't use HTTP for #{5 - time_since_last_request.to_i} second(s)"
          end
        end
        
        def http_request_for(verb, uri, params)
          verb  = verb.downcase.capitalize
          raise "unsupported verb `#{verb}'" unless %w(Get Post Head).include?(verb)
          
          klass = Net::HTTP.const_get(verb)
          body  = encode_http_request_params(params)
          
          query = uri.query unless uri.query.empty? if uri.query
          path  = uri.path
          path += "?#{query}" if query
          
          if klass::REQUEST_HAS_BODY
            req = klass.new(path)
            req.body = body
            req
          else
            path += "#{query ? "&" : "?"}#{body}" unless body.empty?
            klass.new(path)
          end
        end

        def encode_http_request_params(params)
          pairs = []
          params.each_with_index do |param, index|
            value = CGI.escape(param)
            if (index % 2).zero?
              pairs << value
            else
              pairs.last << "=#{value}"
            end
          end
          pairs.join("&")
        end
    end
  end
end
